package ru.spb.osll.myfit.server.chart;

import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;

/**
 * User: Artem Bukhonov
 * Email: nerzhulart@gmail.com
 * Date: 24.11.11
 * Time: 14:47
 */
public class JarChartsBuilderLoader implements ChartsBuilderLoader {
  private Map<String, Class<? extends ChartsBuilder>> m_builderClasses = new HashMap<String, Class<? extends ChartsBuilder>>();
  private ClassLoader m_classLoader;

  public JarChartsBuilderLoader(String dirName) throws IOException {
    File dir = new File(dirName);
    if (!dir.isDirectory()) {
      throw new IOException("Given path is not a directory");
    }
    List<File> files = new ArrayList<File>();
    String[] fileList = dir.list(new FilenameFilter() {
      @Override
      public boolean accept(File dir, String name) {
        return name.endsWith(".jar");
      }
    });
    for (String fileName : fileList) {
      File file = new File(dir, fileName);
      files.add(file);    
    }
    
    URL[] urlsArray = new URL[files.size()];
    for (int i = 0; i < files.size(); ++i) {
      urlsArray[i] = files.get(i).toURI().toURL();
    }
    m_classLoader = new URLClassLoader(urlsArray, this.getClass().getClassLoader());
    loadClasses(files);
  }

  private void loadClasses(List<File> files) throws IOException {
    for (File file : files) {
      JarInputStream stream = new JarInputStream(new FileInputStream(file));
      JarEntry entry;
      while ((entry = stream.getNextJarEntry()) != null) {
        String entryName = entry.getName();
        if (entryName.endsWith(".class")) {
          Class<? extends ChartsBuilder> chartsBuilderClass;
          entryName = entryName.replace('/', '.').replace(".class", "");
          try {
            Class<?> clazz = m_classLoader.loadClass(entryName);
            if (ChartsBuilder.class.isAssignableFrom(clazz)) {
              chartsBuilderClass = (Class<? extends ChartsBuilder>) clazz.asSubclass(ChartsBuilder.class);
              m_builderClasses.put(chartsBuilderClass.getSimpleName(), chartsBuilderClass);
            }

          } catch (Throwable e) {
          }
        }
      }
    }
  }

  public void close() {
    try {
      Class<URLClassLoader> urlClassLoaderClass = java.net.URLClassLoader.class;
      java.lang.reflect.Field ucpField = urlClassLoaderClass.getDeclaredField("ucp");
      ucpField.setAccessible(true);
      Object sunMiscURLClassPath = ucpField.get(m_classLoader);
      java.lang.reflect.Field loadersField = sunMiscURLClassPath.getClass()
              .getDeclaredField("loaders");
      loadersField.setAccessible(true);
      Object javaUtilCollection = loadersField.get(sunMiscURLClassPath);
      for (Object sun_misc_URLClassPath_JarLoader: ((java.util.Collection<?>) javaUtilCollection)
              .toArray()) {
        try {
          java.lang.reflect.Field jarField = sun_misc_URLClassPath_JarLoader
                  .getClass().getDeclaredField("jar");
          jarField.setAccessible(true);
          Object javaUtilJarFile = jarField
                  .get(sun_misc_URLClassPath_JarLoader);
          if (javaUtilJarFile instanceof JarFile) {
            ((java.util.jar.JarFile)javaUtilJarFile).close();
          }
        } catch (Throwable t) {
        }
      }
    } catch (Throwable t) {
    }
  }

  @Override
  public ChartsBuilder getChartsBuilderInstanceByName(String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    ChartsBuilder result;
    if (m_builderClasses.containsKey(className)) {
      Class<? extends ChartsBuilder> chartsBuilderClass = m_builderClasses.get(className);
      result = chartsBuilderClass.newInstance();
    }
    else {
      throw new ClassNotFoundException("Chart builder \"" + className + "\" not found");
    }
    return result;
  }
}
